#!/usr/bin/env python
# -*- coding: utf-8 -*-

import paho.mqtt.client as mqtt
import time
import logging
import threading
import subprocess
import json
import sys
import string

# 日志模块
from log.Inform import Inform
from log.SysInfo import SysInfo
from log.Flow import Flow

# 工具类
from utils.DateUtil import DateUtil
from utils.ShellUtil import ShellUtil

# 模块
from conf.ConfAd import ConfAd
from conf.ConfTv import ConfTv

HOST = "101.132.161.95"
PORT = 61613
CURRENT_CONFIG_TI_ID = 1526528622
class MqttClient():

    def __init__(self, logger, deviceInfo):
        self.logger = logger
        self.sn = deviceInfo["sn"]
        self.deviceInfo = deviceInfo
        # self.client = mqtt.Client(self.sn, clean_session=True, userdata=None, protocol=MQTTv311, transport="tcp")    # clientId不唯一会返回:[WinError 10054] 远程主机强迫关闭了一个现有的连接
        self.client = mqtt.Client(self.sn)
        self.client.username_pw_set("admin", "password")  # 必须设置，否则会返回「Connected with result code 4」
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        self.client.connect(HOST, PORT, 60)
        self.client.loop_forever()

    # 连接成功时
    def on_connect(self, client, userdata, flags, rc):
        # client：客户端实例，即 __init__ 创建的mqtt实例
        # userdata：用户数据，可以在创建新客户端实例时设置或者使用user_data_set(userdata)设置
        # flags：包含代理回复的标志的字典
        # rc：0 -> 连接成功，1 -> 协议版本错误，2 -> 无效的客户端标识，3 -> 服务器无法使用，4 -> 错误的用户名或密码，5 -> 未经授权
        if str(rc) == "0" :
            self.logger.info("[%s] connect success" % self.sn)
            # self.client.reinitialise()

            # 当与mqtt服务器连接成功时，初始化日志线程
            self.initThread()
            # 当与mqtt服务器连接成功时，初始化各个模块
            self.initModule()
            # 发布要订阅的消息，消息的topic为设备sn
            self.client.subscribe(self.sn)
        else:
            self.logger.info("[%s] connect fail with result code：%s" % (self.sn, str(rc)))

    # # 断开连接时
    # def on_disconnect(self, client, userdata, rc) :
    #
    # # 消息送达时
    # def on_publish(self, client, userdata, mid) :
    #
    # # 发送的消息被订阅时
    # def on_subscribe(self, client, userdata, mid, granted_qos) :
    #
    # # 当代理响应取消订阅请求时调用 -> 还没明白啥意思
    # def on_unsubscribe(self, client, userdata, mid) :
    #
    # # 当客户端有日志信息时调用 -> 这里的客户端还没明白是哪里
    # def on_log(self, client, userdata, level, buf) :
    #     # level：MQTT_LOG_INFO，MQTT_LOG_NOTICE，MQTT_LOG_WARNING，MQTT_LOG_ERR，MQTT_LOG_DEBUG
    #     self.logger.info("level is [%s]" , level)

    # 接收到消息
    def on_message(self, client, userdata, msg):
        topic = msg.topic # 为设备序列号，表示该消息是发送给这台设备的
        message = msg.payload.decode("utf-8") # 消息的内容格式为json
        message = json.loads(message)
        # print message
        event = message["eventType"]
        if event == "REBOOT" :
            # message -> {"eventType": "REBOOT", "sn": "G1LQ66N057576"}
            self.reboot(message)
        if event == "CONFIG_TV" :
            # message -> {"eventType": "CONFIG_TV", "sn": "G1LQ66N057576", "configId": 1526528622, "channelList": [{}, {}, {}]}
            self.confTv.conf(message)
        if event == "CONFIG_DEVICE" :
            # message -> {"eventType": "CONFIG_DEVICE", "sn": "G1LQ66N057576", "deviceInfo": {"aliasName": "G1LQ66N057576", "productType": "SERVER", "productClass": "ETV800", "periodic": 180}}
            self.confDevice(message)
        if event == "CREATE_TUNNEL" :
            # message ->{"eventType": "CREATE_TUNNEL", "sn": "G1LQ66N057576", "commands" : ["", ""]}
            self.createTunnel(message)
        if event == "CONFIG_AD" :
            self.confAd.download(message)

    def initThread(self):
        # 周期报文
        self.inform = Inform(self.logger, self.client, self.deviceInfo)
        self.inform.start()
        time.sleep(5)
        # 系统日志
        self.sysInfo = SysInfo(self.logger, self.client, self.deviceInfo)
        self.sysInfo.start()
        time.sleep(5)
        # 流量日志
        self.flow = Flow(self.logger, self.client, self.deviceInfo)
        self.flow.start()

    def initModule(self) :
        # 广告配置模块
        self.confAd = ConfAd(self.logger, self.client, self.sn)
        # 频道配置模块
        self.confTv = ConfTv(self.logger, self.client, self.sn)

    # 向服务端发送消息
    def publish(self, topic, message):
        self.logger.info("[%s] publish [%s]:[%s]" % (self.sn, topic, message))
        self.client.publish(topic, json.dumps(message, ensure_ascii=False), 2)

    # 设备重启
    def reboot(self, message):
        # 校验重启目标是否是本机
        if message["sn"] != self.sn :
            data = {"sn": self.sn, "status": "fasle", "code": "TARGET_ERROR", "msg": "目标序列号错误", "uploadTime": DateUtil.getNowDate()}
            self.publish("REBOOT_CALLBACK", data)
            return
        # TODO 设备重启
        # 重启成功回传
        data = {"sn": self.sn, "status": "true", "code": "SUCCESS", "msg": "重启成功", "uploadTime": DateUtil.getNowDate()}
        self.publish("REBOOT_CALLBACK", data)

    # 配置设备
    def confDevice(self, message):
        # 校验重启目标是否是本机
        if message["sn"] != self.sn :
            data = {"sn": self.sn, "status": "fasle", "code": "TARGET_ERROR", "msg": "目标序列号错误", "uploadTime": DateUtil.getNowDate()}
            self.publish("CONFIG_DEVICE_CALLBACK", data)
            return
        # self.deviceInfo = message["deviceInfo"]
        data = {"sn": self.sn, "status": "true", "code": "SUCCESS", "msg": "设备配置成功", "uploadTime": DateUtil.getNowDate()}
        self.publish("CONFIG_DEVICE_CALLBACK", data)
        # self.inform.reSetDeviceInfo(self.deviceInfo)

    def createTunnel(self, message) :
        results = []
        for command in message["commands"] :
            result = {"command": "createTunnel"}
            print command
            p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
            lines = []
            for line in p.stdout.readlines() :
                print line
                lines.append(line)
            result["lines"] = lines
            results.append(result)
        data = {"sn": self.sn, "tunnelId": message["tunnelId"], "response": results, "status": "true", "code": "SUCCESS", "msg": "设备配置成功", "uploadTime": DateUtil.getNowDate()}
        self.publish("CREATE_TUNNEL_CALLBACK", data)

def command(cmd, timeout=60):
    """执行命令cmd，返回命令输出的内容。
    如果超时将会抛出TimeoutError异常。
    cmd - 要执行的命令
    timeout - 最长等待时间，单位：秒
    """
    p = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True)
    t_beginning = time.time()
    seconds_passed = 0
    while True:
        if p.poll() is not None:
            break
        seconds_passed = time.time() - t_beginning
        if timeout and seconds_passed > timeout:
            p.terminate()
            raise TimeoutError(cmd, timeout)
        time.sleep(0.1)
    return p.stdout.read()

def initLogger() :
    logFormat = "%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s"
    logLevel = logging.WARN
    logging.basicConfig(level=logLevel, format=logFormat)
    logger = logging.getLogger()
    logfilename = "/root/%s.log" % time.strftime('%Y%m%d', time.localtime(time.time()))
    formatter = logging.Formatter(logFormat)
    fh = logging.FileHandler(logfilename)
    fh.setLevel(logLevel)
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    return logger

if __name__ == "__main__":
    logger = initLogger();
    sn = command("uci get sysinfo.@sysinfo[0].serial_num", timeout=3)
    periodic = command("uci get cwmp.@cwmp[0].PeriodicInformInterval")
    period = string.atoi(periodic.strip('\n'))
    deviceInfo = {"sn": sn.strip('\n'), "aliasName": "", "deviceType": "SERVER", "deviceClass": "ETV-800", "periodic": period}

    mqttClient = MqttClient(logger, deviceInfo)
